package com.diao.myhub.controller;

import com.diao.myhub.cache.AllTagCache;
import com.diao.myhub.cache.HotTagCache;
import com.diao.myhub.enums.TagTypeEnum;
import com.diao.myhub.exception.CustomizeError;
import com.diao.myhub.exception.CustomizeException;
import com.diao.myhub.model.bo.TagNode;
import com.diao.myhub.model.dto.HotTagDTO;
import com.diao.myhub.model.param.TagAddParam;
import com.diao.myhub.model.po.Tag;
import com.diao.myhub.model.vo.AllTagsVO;
import com.diao.myhub.service.TagService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@RestController
@RequestMapping("/tag")
@RequiredArgsConstructor
public class TagController {
    private final TagService tagService;
    private final HotTagCache tagCache;
    private final AllTagCache allTagCache;

    @Value("${admin.token}")
    private String token;

    @PostMapping("/admin")
    public Object addTagList(@RequestBody List<TagAddParam> param, @RequestParam String auth) {
        if (!Objects.equals(auth, token)) {
            throw new CustomizeException(CustomizeError.NOT_ALLOWED);
        }
        if (CollectionUtils.isEmpty(param)) {
            return Collections.emptyList();
        }
        List<TagNode> rootTagNodes = tagService.listTagTree();
        HashSet<String> allNameSet = new HashSet<>();
        Map<Long, TagNode> rootIdMap = rootTagNodes.stream().collect(Collectors.toMap(TagNode::getId, o -> o));
        Map<String, TagNode> rootNameMap = rootTagNodes.stream().collect(Collectors.toMap(TagNode::getName, o -> o));
        rootTagNodes.stream().flatMap(rootTag -> {
            allNameSet.add(rootTag.getName());
            return rootTag.getChildren().stream();
        }).map(TagNode::getName).forEach(allNameSet::add);
        // 保存本次新增标签的标签名，保证本次新增标签不重复
        HashSet<String> currentNameSet = new HashSet<>();
        Map<Long, Set<String>> pidNameMap = param.stream()
                .filter(tagParam -> isPrimaryPresent(rootIdMap, rootNameMap, tagParam))
                .collect(Collectors.groupingBy(TagAddParam::getId,
                        reduceChildren(allNameSet, currentNameSet)));
        Map<String, Set<String>> pNameMap = param.stream()
                .filter(tagParam -> !isPrimaryPresent(rootIdMap, rootNameMap, tagParam)
                        && isPrimaryNameValid(tagParam.getName(), currentNameSet, allNameSet))
                .collect(Collectors.groupingBy(TagAddParam::getName, reduceChildren(allNameSet, currentNameSet)));
        // 根据父元素id导入的标签
        List<Tag> idTags = pidNameMap.entrySet().stream()
                .map(entry -> entry.getValue().stream()
                        .map(value -> getNewTag(value, TagTypeEnum.TAG.getTypeCode(), entry.getKey()))
                        .toList()).reduce(new ArrayList<>(), (t1, t2) -> {
                    t1.addAll(t2);
                    return t1;
                });
        List<Tag> newPrimaryTags = pNameMap.keySet().stream()
                .map(pName -> getNewTag(pName, TagTypeEnum.CATEGORY.getTypeCode(), -1L))
                .collect(Collectors.toList());
        tagService.saveBatch(newPrimaryTags);
        List<Tag> nameTags = newPrimaryTags.stream().flatMap(pTag -> {
            String name = pTag.getName();
            Set<String> subSet = pNameMap.get(name);
            if (!CollectionUtils.isEmpty(subSet)) {
                return subSet.stream().map(subName -> getNewTag(subName, TagTypeEnum.TAG.getTypeCode(), pTag.getId()));
            }
            return Stream.empty();
        }).toList();
        ArrayList<Tag> allSubTags = new ArrayList<>(idTags);
        allSubTags.addAll(nameTags);
        tagService.saveBatch(allSubTags);
        allSubTags.addAll(newPrimaryTags);
        // 父元素名导入的
        return allSubTags;
    }

    private boolean isPrimaryPresent(Map<Long, TagNode> rootIdMap, Map<String, TagNode> rootNameMap, TagAddParam param) {
        Long id = param.getId();
        if (!Objects.isNull(id) && !Objects.equals(id, -1L) && rootIdMap.containsKey(id)) {
            return true;
        }
        String name = param.getName();
        if (StringUtils.hasText(name) && rootNameMap.containsKey(name)) {
            param.setId(rootNameMap.get(name).getId());
            return true;
        }
        return false;
    }

    private boolean isPrimaryNameValid(String name, HashSet<String> currentNameSet, HashSet<String> allNameSet) {
        return StringUtils.hasText(name) && !currentNameSet.contains(name) && !allNameSet.contains(name);
    }

    private Tag getNewTag(String name, int type, Long pid) {
        Tag tag = new Tag();
        tag.setName(name);
        tag.setDescription(name);
        tag.setPid(pid);
        tag.setType(type);
        tag.setTrend(0L);
        tag.setQCount(0L);
        tag.setRCount(0L);
        tag.setMTime(System.currentTimeMillis());
        tag.setCTime(tag.getMTime());
        return tag;
    }

    private static Collector<TagAddParam, ?, Set<String>> reduceChildren(HashSet<String> allNameSet,
                                                                         HashSet<String> currentNameSet) {
        return Collectors.reducing(
                new HashSet<>(),
                tp -> {
                    List<TagAddParam> children = tp.getChildren();
                    if (!CollectionUtils.isEmpty(children)) {
                        Set<String> thisSet = children.stream()
                                .map(TagAddParam::getName)
                                .filter(name -> StringUtils.hasText(name)
                                        && !allNameSet.contains(name)
                                        && !currentNameSet.contains(name))
                                .collect(Collectors.toSet());
                        currentNameSet.addAll(thisSet);
                        return thisSet;
                    }
                    return new HashSet<>();
                },
                ((t1, t2) -> {
                    t1.addAll(t2);
                    return t1;
                }));
    }
    @GetMapping("/all")
    public AllTagsVO getHotTags(){
        AllTagsVO allTagsVO = new AllTagsVO();
        allTagsVO.setTopList(tagCache.getHotTags());
        allTagsVO.setAllList(allTagCache.getTagCache());
        return allTagsVO;
    }
}
